/**
 * Copyright (C) 2024 Intel Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations under the License.
 *
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef BACKENDS_TOFINO_BF_P4C_IR_UNIQUE_ID_H_
#define BACKENDS_TOFINO_BF_P4C_IR_UNIQUE_ID_H_

#include <iostream>

#include "lib/cstring.h"
#include "lib/exceptions.h"

namespace P4 {
class JSONGenerator;
class JSONLoader;
}  // namespace P4

namespace P4 {

/** The purpose of this class is to provide a unique identification of an BackendAttached table
 *  per IR::MAU::Table object.  The only way to guarantee to uniqueness is both by name and
 *  by an associated type.  For example, a P4 object could in theory have multiple counters,
 *  but these counters would have to have separate names.  Also, let's say a selector and
 *  and action profile have the same name (which they do in P4-16).  The types of these
 *  BackendAttached tables would be different.
 *
 *  This id can be generated by any IR::MAU::AttachedMemory object.  Furthermore, it is used
 *  to associate per flow enable bits and meter types.
 */
class UniqueAttachedId {
 public:
    cstring name;
    enum type_t {
        INVALID,
        TERNARY_INDIRECT,
        IDLETIME,
        COUNTER,
        METER,
        SELECTOR,
        STATEFUL_ALU,
        ACTION_DATA
    } type = INVALID;

    enum pre_placed_type_t { NO_PP, TIND_PP, ADATA_PP };

    bool operator==(const UniqueAttachedId &uai) const {
        return name == uai.name && type == uai.type;
    }

    bool operator!=(const UniqueAttachedId &uai) const { return !(*this == uai); }

    bool operator<(const UniqueAttachedId &uai) const {
        if (name != uai.name) return name < uai.name;
        if (type != uai.type) return type < uai.type;
        return false;
    }

    operator bool() const { return type != INVALID; }
    bool name_valid() const { return !name.isNull(); }

    UniqueAttachedId() {}
    UniqueAttachedId(cstring n, bool direct, type_t t) {
        type = t;
        if (!(type == IDLETIME || type == TERNARY_INDIRECT || (type == ACTION_DATA && direct))) {
            name = n;
        }
    }

    explicit UniqueAttachedId(pre_placed_type_t ppt) {
        BUG_CHECK(ppt != NO_PP, "Attached ID not initialized correct");
        if (ppt == TIND_PP) type = TERNARY_INDIRECT;
        if (ppt == ADATA_PP) type = ACTION_DATA;
    }

    void toJSON(P4::JSONGenerator &json) const;
    static UniqueAttachedId fromJSON(P4::JSONLoader &json);

    bool has_meter_type() const {
        return type == METER || type == STATEFUL_ALU || type == SELECTOR;
    }

    std::string build_name() const;
};

inline std::ostream &operator<<(std::ostream &out, const UniqueAttachedId::type_t type) {
    switch (type) {
        case UniqueAttachedId::TERNARY_INDIRECT:
            out << "TERNARY_INDIRECT";
            break;
        case UniqueAttachedId::IDLETIME:
            out << "IDLETIME";
            break;
        case UniqueAttachedId::COUNTER:
            out << "COUNTER";
            break;
        case UniqueAttachedId::METER:
            out << "METER";
            break;
        case UniqueAttachedId::SELECTOR:
            out << "SELECTOR";
            break;
        case UniqueAttachedId::STATEFUL_ALU:
            out << "STATEFUL_ALU";
            break;
        case UniqueAttachedId::ACTION_DATA:
            out << "ACTION_DATA";
            break;
        default:
            out << "INVALID";
            break;
    }
    return out;
}

inline bool operator>>(cstring s, UniqueAttachedId::type_t &type) {
    if (!s || s == "" || s == "INVALID")
        type = UniqueAttachedId::INVALID;
    else if (s == "TERNARY_INDIRECT")
        type = UniqueAttachedId::TERNARY_INDIRECT;
    else if (s == "IDLETIME")
        type = UniqueAttachedId::IDLETIME;
    else if (s == "COUNTER")
        type = UniqueAttachedId::COUNTER;
    else if (s == "METER")
        type = UniqueAttachedId::METER;
    else if (s == "SELECTOR")
        type = UniqueAttachedId::SELECTOR;
    else if (s == "STATEFUL_ALU")
        type = UniqueAttachedId::STATEFUL_ALU;
    else if (s == "ACTION_DATA")
        type = UniqueAttachedId::ACTION_DATA;
    else
        return false;
    return true;
}

inline std::ostream &operator<<(std::ostream &out, const UniqueAttachedId &uai) {
    out << uai.name << " " << uai.type;
    return out;
}

/** The UniqueId is a unique identifier for an individual table after table placement.
 *  The UniqueId can be used to build a unique name for the assembler.
 *
 *  This is currently the key for the Memories::Use map, as well as how the tables know if
 *  the allocation is attached to a particular table.
 *
 *  The unique identifier will be unique between each table as long as none of the tables have
 *  the same name.  After TablePlacement, tables that have been split into multiple tables, due
 *  to either stage splits or logical table splits, should have a unique stage_table and
 *  logical_table.  Both of these should be validated by the CheckTableNameDuplicate pass.
 */

class UniqueId {
 public:
    cstring name;
    // The stage table index of a table across multiple stages
    int stage_table = -1;
    // The logical table index of a table in a single stage (i.e. ATCAM tables have multiple
    // logical tables per stage)
    int logical_table = -1;

    bool stage_table_used() const { return stage_table != -1; }
    bool logical_table_used() const { return logical_table != -1; }
    bool is_gw = false;

    // An attached Id for determining a unique name for an Attached Table Name
    UniqueAttachedId a_id;
    enum speciality_t { NONE, ATCAM, DLEFT } speciality = NONE;

    bool operator==(const UniqueId &ui) const;
    bool operator!=(const UniqueId &ui) const { return !(*this == ui); }
    bool operator<(const UniqueId &ui) const;

    bool equal_table(const UniqueId &ui) const;

    std::string build_name() const;
    UniqueId base_match_id() const;

    UniqueId() {}
    explicit UniqueId(cstring n) : name(n) {}
    cstring toString() const { return build_name(); }
};

std::ostream &operator<<(std::ostream &out, const UniqueId &ui);

}  // namespace P4

#endif /* BACKENDS_TOFINO_BF_P4C_IR_UNIQUE_ID_H_ */
